﻿using Dotnet.Utils;
using Microsoft.Extensions.DependencyModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;

namespace Dotnet.Utils.Utility.AssemblyUtil
{
    internal class Test
    {
        public void Init()
        {
            //LoadFile 方法加载指定路径的程序集文件，并返回一个表示该程序集的 Assembly 实例。不会加载依赖项
            Assembly.LoadFile(@"C:\a.dll");

            //LoadFrom 方法加载指定路径的程序集文件，并返回一个表示该程序集的 Assembly 实例。会加载依赖项,但是要注意依赖项版本冲突问题
            Assembly.LoadFrom(@"C:\a.dll");

            //当前运行时程序集
            Assembly.GetExecutingAssembly();
        }
    }

    /// <summary>
    /// 程序集帮助类
    /// </summary>
    public static class AssemblyHelper
    {
        public static IList<Assembly> GetAllAssemblies(string includeAssemblyStartsName = "")
        {
            return Directory.GetFiles(GetAssemblyPath(), $"{includeAssemblyStartsName}*.dll").Select(Assembly.LoadFrom).ToList();
        }

        public static IList<Assembly> GetAllAssemblies()
        {
            string[] Filters = { "dotnet-", "Microsoft.", "mscorlib", "netstandard", "System", "Windows" };
            return DependencyContext.Default?.GetDefaultAssemblyNames().Where(c => c.Name != null && !Filters.Any(c.Name.StartsWith)).Select(Assembly.Load).ToList()!;
        }

        public static List<Assembly> LoadAssemblies(string folderPath, SearchOption searchOption)
        {
            return GetAssemblyFiles(folderPath, searchOption).Select(AssemblyLoadContext.Default.LoadFromAssemblyPath).ToList();
        }

        /// <summary>
        /// 获取程序集依赖项
        /// </summary>
        /// <param name="folderPath"></param>
        /// <param name="searchOption"></param>
        /// <returns></returns>
        public static List<Assembly> GetReferencedAssemblies(Assembly assembly, string search = "")
        {
            return assembly.GetReferencedAssemblies().Where(a => a.Name.Contains(search)).Select(Assembly.Load).ToList();
        }

        public static IEnumerable<string> GetAssemblyFiles(string folderPath, SearchOption searchOption)
        {
            return Directory.EnumerateFiles(folderPath, "*.*", searchOption).Where(s => s.EndsWith(".dll") || s.EndsWith(".exe"));
        }

        /// <summary>
        /// 获取程序集目录
        /// </summary>
        /// <returns></returns>
        public static string GetAssemblyPath() => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

        public static string GetAssemblyPath(Assembly assembly) => Path.GetDirectoryName(assembly.Location);

        /// <summary>
        /// 通过程序集名称
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string GetAssemblyName(Assembly assembly)
        {
            return assembly.GetName().Name;
        }

        /// <summary>
        /// 获取程序集
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Assembly GetAssembly(string path)
        {
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

            //foreach (string dependencyPath in Directory.GetFiles(Path.GetDirectoryName(path), "*.dll"))
            //    Assembly.LoadFrom(dependencyPath);

            return Assembly.LoadFile(path);
        }

        /// <summary>
        /// 获取程序集
        /// </summary>
        /// <param name="assemblyName">程序集名称</param>
        public static Assembly GetAssemblyByName(string assemblyName)
        {
            return Assembly.Load(new AssemblyName(assemblyName));
        }

        /// <summary>
        /// 通过Type获取程序集路径
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string GetAssemblyFullName(Type type)
        {
            return type.Assembly.Location;
        }

        /// <summary>
        /// 通过Type获取程序集
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Assembly GetAssembly(Type type)
        {
            return Assembly.LoadFrom(GetAssemblyFullName(type));
        }

        public static Type[] GetAllTypes(Assembly assembly)
        {
            return assembly.GetTypes();
        }

        /// <summary>
        /// 根据程序集名字得到程序集
        /// </summary>
        /// <param name="assemblyNames"></param>
        /// <returns></returns>
        public static IEnumerable<Assembly> GetAssembliesByName(params string[] assemblyNames) => assemblyNames.Select(o => AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory, $"{o}.dll")));

        /// <summary>
        /// 查找指定条件的类型
        /// </summary>
        public static IEnumerable<Type> FindTypes(Func<Type, bool> predicate) => GetAllAssemblies().SelectMany(c => c.GetTypes()).Where(predicate).ToArray();

        /// <summary>
        /// 查找所有指定特性标记的类型
        /// </summary>
        /// <typeparam name="TAttribute"></typeparam>
        /// <returns></returns>
        public static IEnumerable<Type> FindTypesByAttribute<TAttribute>() where TAttribute : Attribute => FindTypesByAttribute(typeof(TAttribute));

        /// <summary>
        /// 查找所有指定特性标记的类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IEnumerable<Type> FindTypesByAttribute(Type type) => GetAllAssemblies().SelectMany(c => c.GetTypes()).Where(a => a.IsDefined(type, true)).Distinct().ToArray();

        /// <summary>
        /// 查找指定条件的类型
        /// </summary>
        public static IEnumerable<Assembly> FindAllItems(Func<Assembly, bool> predicate) => GetAllAssemblies().Where(predicate).ToArray();

        /// <summary>
        /// 获取程序集List
        /// </summary>
        /// <param name="assemblyName"></param>
        /// <returns></returns>
        public static List<Assembly> GetAssemblies(string directoryPath)
        {
            return (from t in Directory.GetFiles(directoryPath, "*.*", SearchOption.AllDirectories)
                    where t.EndsWith(".exe") || t.EndsWith(".dll")
                    select t into path
                    select Assembly.Load(new AssemblyName(path))).ToList<Assembly>();
        }
        /// <summary>
        /// 从目录中获取所有程序集
        /// </summary>
        /// <param name="directoryPath">目录绝对路径</param>
        public static List<Assembly> GetAssemblies2(string directoryPath)
        {
            return Directory.GetFiles(directoryPath, "*.*", SearchOption.AllDirectories)
                .Where(t => t.EndsWith(".exe") || t.EndsWith(".dll"))
                .Select(path => Assembly.Load(new AssemblyName(path))).ToList();
        }

        /// <summary>
        /// 注入程序集
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            // 获取要加载的 DLL 文件名
            string assemblyName = new AssemblyName(args.Name).Name;

            // 根据 DLL 文件名查找依赖项 DLL 文件
            string dependentDll = Path.Combine(Path.GetDirectoryName(args.RequestingAssembly.Location), assemblyName + ".dll");

            // 如果依赖项 DLL 文件存在，则加载并返回
            if (File.Exists(dependentDll))
            {
                return Assembly.LoadFrom(dependentDll);
            }

            // 如果依赖项 DLL 文件不存在，则返回 null
            return null;
        }
    }

    public class RuntimeHelper
    {
        /// <summary>
        /// 获取项目程序集，排除所有的系统程序集(Microsoft.***、System.***等)、Nuget下载包
        /// </summary>
        /// <returns></returns>
        public static IList<Assembly> GetAllAssemblies(string includeAssemblyStartsName = "")
        {
            var list = new List<Assembly>();

            //Linux环境下此行代码会报错，需要在项目中添加<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>属性
            var deps = DependencyContext.Default;

            //排除所有的系统程序集、Nuget下载包
            var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type != "package" && lib.Name.StartsWith(includeAssemblyStartsName));
            foreach (var lib in libs)
            {
                try
                {
                    var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name));
                    list.Add(assembly);
                }
                catch (Exception)
                {
                    // ignored
                }
            }
            return list;
        }

        public static IList<Type> GetAllTypes()
        {
            var list = new List<Type>();
            foreach (var assembly in GetAllAssemblies())
            {
                foreach (var typeInfo in assembly.DefinedTypes)
                {
                    list.Add(typeInfo.AsType());
                }
            }
            return list;
        }

        public static IList<Assembly> GetAllAssemblies2(string includeAssemblyStartsName = "")
        {
            return DependencyContext.Default.GetDefaultAssemblyNames().Where(lib => (bool)(lib.Name?.StartsWith(includeAssemblyStartsName))).Select(Assembly.Load).ToList();
        }

        public static IList<Type> GetAllTypes2() => GetAllAssemblies2().SelectMany(t => t.GetTypes()).ToList();

        public static Assembly GetAssembly(string assemblyName)
        {
            return GetAllAssemblies().FirstOrDefault(assembly => assembly.FullName.Contains(assemblyName));
        }

        public static IList<Type> GetTypesByAssembly(string assemblyName)
        {
            var list = new List<Type>();
            var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(assemblyName));
            var typeInfos = assembly.DefinedTypes;
            foreach (var typeInfo in typeInfos)
            {
                list.Add(typeInfo.AsType());
            }
            return list;
        }

        public static Type? GetImplementType(string typeName, Type baseInterfaceType)
        {
            return GetAllTypes().FirstOrDefault(t =>
            {
                if (t.Name == typeName &&
                    t.GetTypeInfo().GetInterfaces().Any(b => b.Name == baseInterfaceType.Name))
                {
                    var typeInfo = t.GetTypeInfo();
                    return typeInfo.IsClass && !typeInfo.IsAbstract && !typeInfo.IsGenericType;
                }
                return false;
            });
        }
    }

    /// <summary>
    /// 基础特性
    /// </summary>
    public abstract class AttributeBase : Attribute
    {
        /// <summary>
        /// 获取描述
        /// </summary>
        /// <returns></returns>
        public abstract string Description();
    }
}